/**
*  \file secacc.c
*
*  \brief   Security Access
*
*  \author  Michael J. Hohmann <michael.hohmann@linde-mh.de>
*
*
*/

/*===============================================[ private includes  ]================================================*/
#define SECACC_LINKAGE
#include "secacc.h"
#include "secacc_crypto.h"
#include "../cfg/secacc_cfg.h"

/*===============================================[ private defines   ]================================================*/

/*===============================================[ private datatypes ]================================================*/

/*===============================================[ private variables ]================================================*/
static enum secacc_state secacc_security_state;
static U32 secacc_code;
static U8 secacc_expected_service;

/*===============================================[ public variables  ]================================================*/

/*===============================================[ private functions ]================================================*/
static enum kwp_response_code secacc_seed( BO condition, U8  *dat, U16 *len, enum secacc_state state);
static enum kwp_response_code secacc_key( U8 *dat, U16 *len, enum secacc_state state );
static U32 secacc_calculate_key( U32 seed, enum secacc_state state );
static void secacc_u32_copy( U8 *dat, U32 value );
static U32 secacc_u32( U8 *dat );

/*====================================================================================================================*/

RC secacc_initialize( void )
{
   secacc_security_state=secacc_state_default;
   secacc_expected_service=0;
   return RC_SUCCESS;
}

enum secacc_state secacc_get_state( void )
{
   return secacc_security_state;
}

void secacc_set_state( enum secacc_state state )
{
   secacc_security_state = state;
}

void secacc_reset_state( void )
{
   secacc_security_state=secacc_state_default;
}

enum kwp_response_code secacc_process_request( BO condition, U8  *dat, U16 *len)
{


   switch( dat[0] )
   {
#if SECACC_12
      case 0x01:  return secacc_seed( condition, dat, len, secacc_state_programming );
      case 0x02:  return secacc_key ( dat, len, secacc_state_programming );
#else
      UNREFERENCED(condition);
#endif


#if SECACC_34
      case 0x03:  return secacc_seed( TRUE,  dat, len, secacc_state_service);
      case 0x04:  return secacc_key ( dat, len, secacc_state_service );
#endif

#if SECACC_56
      case 0x05:  return secacc_seed( TRUE, dat, len, secacc_state_eopl);
      case 0x06:  return secacc_key ( dat, len, secacc_state_eopl );
#endif
      default:    return KWP_RESPONSE_CODE_SUBFUNCTIONNOTSUPPORTED;
   }
}

/** Process Seed Request
*
*  \param dat Request
*  \param len Length of the request and after calling length of the response.*
*  \param state Desired State.
*
*  \return kwp response code.
**/
static enum kwp_response_code secacc_seed( BO condition, U8  *dat, U16 *len, enum secacc_state state)
{
   U8 rv;

   if (*len != 1) /* Wrong Length */
   {
      rv = KWP_RESPONSE_CODE_INVALIDFORMAT;
   }
   else
   {
      if( FALSE == condition )
      {
         rv = KWP_RESPONSE_CODE_CONDITIONSNOTCORRECT;
      }
      else
      {
         if( state == secacc_security_state )
         {
            /* ECU already unlocked */
            dat[1] = 0;
            dat[2] = 0;
            *len   = 3;
            rv = KWP_RESPONSE_CODE_SUCCESS;
         }
         else
         {
          U32 seed = secacc_random();
          secacc_code = secacc_calculate_key( seed, state );
          secacc_expected_service=dat[0]+1;
          *len = 5;
          secacc_u32_copy( &dat[1], seed );
          rv = KWP_RESPONSE_CODE_SUCCESS;
         }
      }
   }
   return rv;
}

/** Process Key Request */
static enum kwp_response_code secacc_key( U8 *dat, U16 *len, enum secacc_state state)
{
   U8 rv;

   if (*len != 5)
   {  /* Invalid length */
      rv = KWP_RESPONSE_CODE_INVALIDFORMAT;
   }
   else
   {
      if ( secacc_expected_service == dat[0])
      {
         /* Securitysecacc_state_access in progress */
         if( secacc_u32(&dat[1]) == secacc_code  )
         {  /* valid key */
            secacc_security_state = state;
            *len = 1;
            rv = KWP_RESPONSE_CODE_SUCCESS;
         }
         else
         {  /* invalid key */
            rv = KWP_RESPONSE_CODE_INVALIDKEY;
         }
      }
      else
      {  /* request sequence error */
         rv = KWP_RESPONSE_CODE_REQUESTSEQUENCEERROR;
      }
   }
   return rv;
}

/** Calculate Key
*
*  \param seed Input value.
*  \param state Kind of algorithm.
*
*  \return Calculated Key
*/
static U32 secacc_calculate_key( U32 seed, enum secacc_state state )
{
   switch(state)
   {
      case secacc_state_programming: return secacc_crypto_12( seed );
      case secacc_state_service    : return secacc_crypto_34( seed );
      case secacc_state_eopl       : return secacc_crypto_56( seed );
      default                      : return 0; /* will never happen */
   }
}

/** Copy 32Bit word into 4 Byte array.
*
*  \param dat Pointer to the destination.
*  \param value To by copied.
*/
static void secacc_u32_copy( U8 *dat, U32 value )
{
   dat[3] = (U8)(value >> 24);
   dat[2] = (U8)(value >> 16);
   dat[1] = (U8)(value >> 8);
   dat[0] = (U8)value;
}

/**  Read 32 Bit word from given address.
*
* \param dat Source Pointer
*
* \return 32Bit value
*/
static U32 secacc_u32(U8 *dat )
{
   return ((U32)dat[3]<<24)+((U32)dat[2]<<16)+((U32)dat[1]<<8)+dat[0];
}

/***********************************************************************************************************************
*                             Copyright 2010 Linde Material Handling. All rights reserved.                             *
***********************************************************************************************************************/
